﻿using Core.Network;
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

namespace Core.Res
{
    using AssetDict = Dictionary<string, BundleLoader.Asset>;
    using BundleDict = Dictionary<string, BundleLoader.Bundle>;

    public partial class BundleLoader : Component, IAwake, ILoader
    {
        private AssetDict assets = new();
        private BundleDict bundles = new();
        private HashSet<string> missing = new();

        private PrefixDict mPrefixDict;
        private RelyQueue mQueue = new(10);
        public ResDict ResDict { get; } = new();

        public string RWPath;
        public string ROPath;

        public void Awake()
        {
            mPrefixDict = new PrefixDict($"Assets/Proj/Games/{G.Name}/");

            RWPath = Path.Combine(Application.persistentDataPath, "abs");
            ROPath = Path.Combine(Application.streamingAssetsPath, "abs");

            string fullPath = Path.Combine(ROPath, $"game.dat");

            G.Get<NetworkSystem>().Load(fullPath, OnLoadComplete);
        }

        public override void Dispose()
        {
            if (IsDisposed) return;
            base.Dispose();

            //TODO: 清理资源
        }

        private void OnLoadComplete(IWebTask task)
        {
            if (task.Status != ETaskStatus.Done) return;

            string str = System.Text.Encoding.Default.GetString(task.Data);
            str = Utils.Crypto.Decrypt(str, "fdagjkfhalhfgdua");
            var json = JsonUtility.FromJson<ResList>(str);
            Debug.LogError(json);
            ResDict.SetData(json);
        }

        public bool CheckAsset(string name)
        {
            return assets.ContainsKey(name) || missing.Contains(name);
        }

        public IAsset GetAsset(string name)
        {
            if (string.IsNullOrWhiteSpace(name)) return null;

            name = mPrefixDict[name];

            return assets.GetValueOrDefault(name);
        }

        public IAsset LoadSync(string name)
        {
            if (string.IsNullOrWhiteSpace(name)) return null;

            name = mPrefixDict[name];

            if (assets.ContainsKey(name) && mQueue.Contains(name) == false)
            {
                return assets[name];
            }

            var assetInfo = ResDict.GetAsset(name);
            if (assetInfo == null)
            {
                return null;
            }

            var bundleInfo = ResDict.GetBundle(assetInfo.Bundle);
            if (bundleInfo == null)
            {
                return null;
            }

            var bundle = new Bundle(this);
            bundle.SetInfo(name);
            bundle.LoadSync();

            var vo = new Asset(this);
            vo.SetInfo(name);
            vo.LoadSync();

            if (assets.ContainsKey(name))
            {
                return assets[name];
            }

            return null;
        }

        private List<string> mRelyAssets = new();
        private List<string> mRelyBundles = new();

        private void GatherRely(string asset_name, ref List<string> bundles, ref List<string> assets)
        {
            asset_name = mPrefixDict[asset_name];

            var assetInfo = ResDict.GetAsset(asset_name);
            if (assetInfo == null)
            {
                Debug.LogError($"Can not load asset '{asset_name}'. it's not exist");
                return;
            }

            var bundleInfo = ResDict.GetBundle(assetInfo.Bundle);
            if (bundleInfo == null)
            {
                Debug.LogError($"Can not load bundle '{assetInfo.Bundle}'. it's not exist");
                return;
            }

            if (asset_name.EndsWith(".spriteatlas") ||
                asset_name.EndsWith(@".shadervariants"))
            {
                if (assets.Contains(assetInfo.Path) == false)
                {
                    assets.Add(assetInfo.Path);
                }

                if (bundles.Contains(bundleInfo.Name) == false)
                {
                    bundles.Add(bundleInfo.Name);
                }

                return;
            }

            var relies = assetInfo.Relies;
            if (relies != null)
            {
                for (int i = 0; i < relies.Length; i++)
                {
                    GatherRely(relies[i], ref bundles, ref assets);
                }
            }

            if (assets.Contains(assetInfo.Path) == false)
            {
                assets.Add(assetInfo.Path);
            }

            if (bundles.Contains(bundleInfo.Name) == false)
            {
                bundles.Add(bundleInfo.Name);
            }
        }

        public async ETTask<IAsset> LoadAsync(string name)
        {
            if (string.IsNullOrWhiteSpace(name)) return null;

            name = mPrefixDict[name];

            if (assets.ContainsKey(name) && mQueue.Contains(name) == false)
            {
                return assets[name];
            }

            mRelyAssets.Clear();
            mRelyBundles.Clear();

            GatherRely(name, ref mRelyBundles, ref mRelyAssets);

            mRelyAssets.Remove(name);

            for (int i = 0; i < mRelyBundles.Count; i++)
            {
                var bundle_name = mRelyBundles[i];
                if (bundles.ContainsKey(bundle_name))
                {
                    continue;
                }

                Bundle vo = new Bundle(this);
                vo.SetInfo(bundle_name);
                mQueue.Enqueue(vo);
            }

            Asset asset = new Asset(this);
            asset.CreateAwaiter();
            asset.SetInfo(name);

            for (int i = 0; i < mRelyAssets.Count; i++)
            {
                var asset_name = mRelyAssets[i];
                if (assets.ContainsKey(asset_name))
                {
                    continue;
                }

                Asset vo = new Asset(this);
                vo.SetInfo(asset_name);
                mQueue.Enqueue(vo);

                asset.Rely.Add(asset_name);
            }

            mQueue.Enqueue(asset);

            await WaitForAsync(asset);

            if (name.EndsWith(".unity") || assets.ContainsKey(name) == false)
            {
                return null;
            }

            return assets[name];
        }

        private ETTask WaitForAsync(LoadAwaiter vo)
        {
            return vo.Awaiter.Task;
        }

        public ETTask LoadAsync(IEnumerable<string> paths, Action<ILoadProgress> func)
        {
            if (paths == null) return default;

            mRelyAssets.Clear();
            mRelyBundles.Clear();

            foreach (var path in paths)
            {
                GatherRely(path, ref mRelyBundles, ref mRelyAssets);
            }

            for (int i = 0; i < mRelyBundles.Count; i++)
            {
                var bundle_name = mRelyBundles[i];
                if (bundles.ContainsKey(bundle_name))
                {
                    continue;
                }

                Bundle vo = new Bundle(this);
                vo.SetInfo(bundle_name);
                mQueue.Enqueue(vo);
            }

            var batch = new Batch(this);
            batch.CreateAwaiter();

            for (int i = 0; i < mRelyAssets.Count; i++)
            {
                var asset_name = mRelyAssets[i];
                if (assets.ContainsKey(asset_name) || asset_name.EndsWith(".unity"))
                {
                    continue;
                }

                var vo = new Asset(this);
                vo.SetInfo(asset_name);
                mQueue.Enqueue(vo);

                batch.Rely.Add(asset_name);
            }

            func?.Invoke(batch);
            mQueue.Enqueue(batch);

            return batch.Awaiter.Task;
        }

        public void Update()
        {
            mQueue.Update();
        }
    }
}
